package procd

import (
	"io"
	"os"
	"os/exec"

	"k8s.io/klog/v2"
)

type Task interface {
	Run()
}

type SimpleTask struct {
	f func()
}

func (st *SimpleTask) Run() {
	st.f()
}

type DaemonTask interface {
	Task
	IsAlive() bool
	Terminate()
}

type ScheduledTask interface {
	Task
	WaitMillis() int64
}

type ScheduledTaskImpl struct {
	f      func()
	millis int64
}

func (st *ScheduledTaskImpl) Run() {
	st.f()
}

func (st *ScheduledTaskImpl) WaitMillis() int64 {
	return st.millis
}

func NewScheduledTask(f func(), interval int64) ScheduledTask {
	return &ScheduledTaskImpl{
		f:      f,
		millis: interval,
	}
}

type DaemonCmdTask struct {
	cmd     []string
	proc    *os.Process
	preRun  func()
	postRun func()
	writers []io.Writer
}

func NewDaemonCmdTask(cmd ...string) *DaemonCmdTask {
	return &DaemonCmdTask{
		cmd:     cmd,
		preRun:  func() {},
		postRun: func() {},
		writers: make([]io.Writer, 0),
	}
}

func (dct *DaemonCmdTask) RegisterPreRun(f func()) {
	dct.preRun = f
}

func (dct *DaemonCmdTask) RegisterPostRun(f func()) {
	dct.postRun = f
}

func (dct *DaemonCmdTask) AttachWriterToStdout(w io.Writer) {
	dct.writers = append(dct.writers, w)
}

func (dct *DaemonCmdTask) Run() {
	if len(dct.cmd) == 0 {
		panic("Command must not be empty")
	}
	var cmd *exec.Cmd = nil
	if len(dct.cmd) == 1 {
		cmd = exec.Command(dct.cmd[0])
	} else {
		cmd = exec.Command(dct.cmd[0], dct.cmd[1:]...)
	}
	dct.preRun()

	if len(dct.writers) != 0 {
		writer := io.MultiWriter(dct.writers...)
		pipe, _ := cmd.StdoutPipe()
		err, _ := cmd.StderrPipe()
		go func() {
			io.Copy(writer, pipe)
		}()
		go func() {
			io.Copy(writer, err)
		}()
		defer pipe.Close()
	}

	err := cmd.Start()
	if err != nil {
		klog.Errorf("Error executing: %v. Err: %v", dct.cmd, err)
		return
	}

	dct.proc = cmd.Process
	err = cmd.Wait()
	if err != nil {
		klog.Error(err)
		dct.proc = nil
	}

	dct.proc = nil

	defer dct.postRun()
}
func (dct *DaemonCmdTask) IsAlive() bool {
	if dct.proc == nil {
		return false
	}
	pid := dct.proc.Pid
	_, err := os.FindProcess(pid)
	return err == nil
}
func (dct *DaemonCmdTask) Terminate() {
	if dct.proc == nil {
		return
	}
	err := dct.proc.Kill()
	if err != nil {
		klog.Error(err)
	}
}
